package com.boat.framework.common.spring.transaction;

import java.lang.reflect.UndeclaredThrowableException;

import org.springframework.lang.Nullable;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;

import com.boat.framework.common.monitor.event.TrxRollbacked;
import com.boat.framework.common.monitor.logs.EventLog;

/**
 * 事务模板
 * 
 * 示例：
 * 有返回值：SpringUtils.trxTemplate.execute(status -> {return result;});
 * 无返回值：SpringUtils.trxTemplate.executeWithoutResult(status -> {});
 * 
 * @author 周江
 *
 */
public class TrxTemplate extends TransactionTemplate{
	
	private static final long serialVersionUID = 568438332779078051L;

	/**
	 * 创建事务模板。
	 * 
	 * propagationBehavior取值如：
	 * TransactionDefinition.PROPAGATION_REQUIRED
	 * TransactionDefinition.PROPAGATION_REQUIRES_NEW
	 * 
	 * @param transactionManager
	 * @param propagationBehavior
	 */
	public TrxTemplate(PlatformTransactionManager transactionManager, int propagationBehavior) {
		this.setTransactionManager(transactionManager);
		this.setPropagationBehavior(propagationBehavior);
	}
	
	/**
	 * 重载主流程，修改日志打印格式和增加事务预警
	 */
	@Override
	@Nullable
	public <T> T execute(TransactionCallback<T> action) throws TransactionException {
		Assert.state(this.getTransactionManager() != null, "No PlatformTransactionManager set");

		if (this.getTransactionManager() instanceof CallbackPreferringPlatformTransactionManager) {
			return ((CallbackPreferringPlatformTransactionManager) this.getTransactionManager()).execute(this, action);
		}
		else {
			TransactionStatus status = this.getTransactionManager().getTransaction(this);
			T result;
			try {
				result = action.doInTransaction(status);
			}
			catch (RuntimeException | Error ex) {
				// Transactional code threw application exception -> rollback
				rollbackOnException(status, ex);
				throw ex;
			}
			catch (Throwable ex) {
				// Transactional code threw unexpected exception -> rollback
				rollbackOnException(status, ex);
				throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
			}
			this.getTransactionManager().commit(status);
			return result;
		}
	}

	/**
	 * Perform a rollback, handling rollback exceptions properly.
	 * @param status object representing the transaction
	 * @param ex the thrown application exception or error
	 * @throws TransactionException in case of a rollback error
	 */
	private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
		Assert.state(this.getTransactionManager() != null, "No PlatformTransactionManager set");

		logger.warn("发生事务回滚，请检查相关数据是否保持一致。错误消息："+ ex.getMessage());
		//打印预警信息
		EventLog.log(new TrxRollbacked(ex));
		
		try {
			this.getTransactionManager().rollback(status);
		}
		catch (TransactionSystemException ex2) {
			logger.error("Application exception overridden by rollback exception", ex);
			ex2.initApplicationException(ex);
			throw ex2;
		}
		catch (RuntimeException | Error ex2) {
			logger.error("Application exception overridden by rollback exception", ex);
			throw ex2;
		}
	}
}
